# Copyright 2012, the Dart project authors.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#     * Neither the name of Google LLC nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require "uri"

class DartURI
  private NEEDS_NO_ENCODING = Regex.new %q{^[\-\.0-9A-Za-z~]*$}
  private HEX_DIGITS        = "0123456789ABCDEF"
  private ENCODE_FULL_TABLE = [
    0x0000,
    0x0000,
    0xFFDA,
    0xAFFF,
    0xFFFF,
    0x87FF,
    0xFFFE,
    0x47FF,
  ] of Int32
  private UNRESERVED_2396_TABLE = [
    0x0000,
    0x0000,
    0x6782,
    0x03FF,
    0xFFFE,
    0x87FF,
    0xFFFE,
    0x47FF,
  ] of Int32

  # Encodes the string *uri* using percent-encoding to make it safe for literal
  # use as a full URI.
  #
  # All characters except uppercase and lowercase letters, digits, and the
  # characters `!#$&'()*+,-./:;=?@_~` are percent-encoded. This is the set of
  # characters specified in ECMA-262 version 5.1 for the encodeURI function.
  #
  # NOTE: This is a Crystal port of the Dart
  # [Uri.encodeFull](https://api.dart.dev/stable/2.13.4/dart-core/Uri/encodeFull.html)
  # method
  # ([src](https://github.com/dart-lang/sdk/blob/2.14.0-371.0.dev/sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart#L875)).
  def self.encode_full(uri : String) : String
    uri_encode(ENCODE_FULL_TABLE, uri, "UTF-8", false)
  end

  # Encode the string *component* using percent-encoding to make it
  # safe for literal use as a URI component.
  #
  # All characters except uppercase and lowercase letters, digits and
  # the characters `-_.!~*'()` are percent-encoded. This is the
  # set of characters specified in RFC 2396 and which is
  # specified for the encodeUriComponent in ECMA-262 version 5.1.
  #
  # When manually encoding path segments or query components, remember
  # to encode each part separately before building the path or query
  # string.
  #
  # Example:
  # ```
  # request = "http://example.com/search=Crystal"
  # encoded = DartURI.encode_component(request)
  # puts encoded # => http%3A%2F%2Fexample.com%2Fsearch%3DCrystal
  # ```
  def self.encode_component(component : String) : String
    uri_encode(UNRESERVED_2396_TABLE, component, "UTF-8", false)
  end

  private def self.uri_encode(canonical_table : Array(Int32), text : String, encoding : String, space_to_plus : Bool) : String
    return text if text.valid_encoding? && NEEDS_NO_ENCODING.matches? text

    # Encode the string into bytes then generate an ASCII only string
    # by percent encoding selected bytes.
    result = String::Builder.new
    bytes = text.encode(encoding)
    bytes.each do |byte|
      if byte < 128 && ((canonical_table[byte >> 4] & (1 << (byte & 0x0F))) != 0)
        result << byte.unsafe_chr
      elsif space_to_plus && byte == 0x20
        result << '+'
      else
        result << '%'
        result << HEX_DIGITS[(byte >> 4) & 0x0F]
        result << HEX_DIGITS[byte & 0x0F]
      end
    end

    result.to_s
  end
end
